// Copyright (c) 2023 Presto Labs Pte. Ltd.
// Author: tianqiang

#include "coin2/exchange/uniswap/order_v3/parser.h"

#include <type_traits>

#include "coin2/exchange/uniswap/api_order/grpc/grpc_trade_client.h"

namespace coin2::exchange::uniswap::order_v3 {

void UniswapParser::ParseAccountBalance(const std::shared_ptr<GrpcContext>& grpc_context) {
  if (!grpc_context->status.ok()) {
    LOG(ERROR) << "[ParseAccountBalance] grpc response status error: "
               << grpc_context->status.error_message();
    result_.set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
    return;
  }

  auto response = std::dynamic_pointer_cast<AccountBalanceResponse>(grpc_context->response);
  if (!response) {
    LOG(ERROR) << "[ParseAccountBalance] grpc response cast error: "
               << grpc_context->response->DebugString();
    result_.set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
    return;
  }
  if (response->status() != "success") {
    LOG(ERROR) << "[ParseAccountBalance] api response status error: " << response->DebugString();
    result_.set_error_code(OrderErrorCode::ORDER_API_ERROR);
    return;
  }

  auto account_balance = ParseBalance(response);
  if (!account_balance.has_exchange() || !account_balance.has_market_type()) {
    result_.set_error_code(OrderErrorCode::ORDER_API_ERROR);
    return;
  }
  result_.set_type(MsgType::ACCOUNT_BALANCE);
  *result_.mutable_account_balance() = std::move(account_balance);
}

void UniswapParser::ParseNftTokenInfo(const std::shared_ptr<GrpcContext>& grpc_context) {
  if (!grpc_context->status.ok()) {
    LOG(ERROR) << "[" << __func__
               << "] grpc response status error: " << grpc_context->status.error_message();
    result_.set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
    return;
  }

  auto response = std::dynamic_pointer_cast<NftTokenInfoResponse>(grpc_context->response);
  if (!response) {
    LOG(ERROR) << "[" << __func__
               << "] grpc response cast error: " << grpc_context->response->DebugString();
    result_.set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
    return;
  }
  auto request = std::dynamic_pointer_cast<NftTokenInfoRequest>(grpc_context->request);
  if (!request) {
    LOG(ERROR) << "[" << __func__
               << "] grpc request cast error: " << grpc_context->request->DebugString();
    result_.set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
    return;
  }
  if (response->status() != "success") {
    LOG(ERROR) << "[" << __func__ << "] api response status error: " << response->DebugString();
    result_.set_error_code(OrderErrorCode::ORDER_API_ERROR);
    return;
  }

  NftTokenInfo nft_token_info;

  nft_token_info.set_wallet_address(response->wallet_address());
  nft_token_info.set_symbol(request->symbol());
  *nft_token_info.mutable_tokenid_to_nft_info() = response->tokenid_to_nft_info();

  result_.set_type(MsgType::NFT_TOKEN_INFO);
  *result_.mutable_nft_token_info() = std::move(nft_token_info);
}

// NOTE: when field exchange and market_type are set, which means parsed successfully
AccountBalance UniswapParser::ParseBalance(
    const std::shared_ptr<AccountBalanceResponse>& response) {
  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalance(response, &raw_balance_map);
  if (!success) {
    return {};
  }
  for (auto it = raw_balance_map.begin(); it != raw_balance_map.end();) {
    if (it->first.size() < 1 || it->first[0] != 'W') {
      ++it;
      continue;
    }
    auto find = raw_balance_map.find(it->first.substr(1));
    if (find == raw_balance_map.end()) {
      ++it;
      continue;
    }
    find->second.set_total(find->second.total() + it->second.total());
    find->second.set_hold(find->second.hold() + it->second.hold());
    find->second.set_available(find->second.available() + it->second.available());
    it = raw_balance_map.erase(it);
  }

  AccountBalance acc_balance;
  acc_balance.set_exchange("Uniswap");
  acc_balance.set_market_type("Spot");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeUpper()) {
    auto* balance = acc_balance.add_each_balance();
    if (raw_balance_map.count(native_currency) == 0) {
      balance->set_total(0);
      balance->set_hold(0);
      balance->set_available(0);
    } else {
      *balance = std::move(raw_balance_map.at(native_currency));
    }
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }

  return acc_balance;
}

bool UniswapParser::ParseRawBalance(
    const std::shared_ptr<AccountBalanceResponse>& response,
    RawBalanceMap* raw_balance_map) {
  for (const auto& kv : response->currency_to_balance()) {
    std::string currency(kv.first);
    CurrencyBalance& currency_balance = (*raw_balance_map)[currency];
    currency_balance.set_currency_native(std::move(currency));
    auto avail = kv.second.available();
    auto hold = kv.second.hold();
    currency_balance.set_available(avail);
    currency_balance.set_total(avail + hold);
    currency_balance.set_hold(hold);
  }

  return true;
}

void UniswapParser::ParseSubmitResponse(const std::shared_ptr<GrpcContext>& grpc_context) {
  const auto order_context = grpc_context->oc;

  result_.set_type(MsgType::SUBMIT_RESPONSE);
  auto resp = result_.mutable_submit_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_grpc_status(grpc_context->status.error_code());
  resp->set_timestamp(GetCurrentTimestamp());
  resp->set_success(false);

  if (!grpc_context->status.ok()) {
    LOG(ERROR) << "[ParseSubmitResponse] grpc response status error: "
               << grpc_context->status.error_message();
    resp->set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
    auto error_code = grpc_context->status.error_code();
    auto int_error_code = static_cast<std::underlying_type_t<decltype(error_code)>>(error_code);
    resp->set_exchange_error_code(std::to_string(int_error_code));
    resp->set_exchange_error_msg(grpc_context->status.error_message());
    return;
  }

  auto response = std::dynamic_pointer_cast<SubmitOrderResponse>(grpc_context->response);
  if (!response) {
    LOG(ERROR) << "[ParseSubmitResponse] grpc response cast error: "
               << grpc_context->response->DebugString();
    resp->set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
    return;
  }

  auto exchange_error_code = response->status();
  auto exchange_error_msg = response->error_message();

  if (exchange_error_code != "success" || !exchange_error_msg.empty()) {
    resp->set_error_code(OrderErrorCode::ORDER_API_ERROR);
    resp->set_exchange_error_code(exchange_error_code);
    resp->set_exchange_error_msg(exchange_error_msg);
    LOG(ERROR) << "[ParseSubmitResponse] api response error\n" << response->DebugString();
    return;
  }

  auto product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();
  auto fill = product_fill_bundle->add_each_fill();

  fill->set_symbol(order_context->order_spec().product);
  fill->set_exchange_order_id(response->tx_hash());
  fill->set_price(response->fill_price());
  fill->set_qty(order_context->order_qty());
  fill->set_fill_id(order_context->order_spec().product + "|" + response->tx_hash());
  fill->set_side(order_context->order_side());
  fill->set_fill_type(coin::proto::FillType::UNKNOWN_FILL_TYPE);
  fill->set_fill_timestamp(GetCurrentTimestamp());

  result_.set_type(MsgType::ORDER_FILL);

  resp->set_exchange_order_id(response->tx_hash());
  resp->set_success(true);
}

void UniswapParser::ParseProvideLiquidity(const std::shared_ptr<GrpcContext>& grpc_context) {
  if (!grpc_context->status.ok()) {
    LOG(ERROR) << "[ParseProvideLiquidity] grpc response status error: "
               << grpc_context->status.error_message();
    return;
  }

  auto response = std::dynamic_pointer_cast<ProvideLiquidityResponse>(grpc_context->response);
  if (!response) {
    LOG(ERROR) << "[ParseProvideLiquidity] grpc response cast error: "
               << grpc_context->response->DebugString();
    return;
  }

  LOG(INFO) << "ParseProvideLiquidity: " << response->DebugString();
}

void UniswapParser::ParseWithdrawLiquidity(const std::shared_ptr<GrpcContext>& grpc_context) {
  if (!grpc_context->status.ok()) {
    LOG(ERROR) << "[ParseWithdrawLiquidity] grpc response status error: "
               << grpc_context->status.error_message();
    return;
  }

  auto response = std::dynamic_pointer_cast<WithdrawLiquidityResponse>(grpc_context->response);
  if (!response) {
    LOG(ERROR) << "[ParseWithdrawLiquidity] grpc response cast error: "
               << grpc_context->response->DebugString();
    return;
  }

  LOG(INFO) << "ParseWithdrawLiquidity: " << response->DebugString();
}

auto UniswapParser::ParseRawMessage(const RawMessage& msg) -> const ParseResult& {
  result_.Clear();
  msg_ = &msg;
  sub_topic_id_ = static_cast<OrderSubTopicId>(msg_->type);

  if (msg.type != OrderSubTopicId::GRPC_MESSAGE) {
    LOG(ERROR) << "unsupport type: " << msg.type;
    return result_;
  }

  auto grpc_context = msg.grpc_context;

  switch (grpc_context->query_type) {
    case coin2::exchange::uniswap::api_order::grpc::GRPC_QUERY_ACCOUNT_BALANCE:
      ParseAccountBalance(grpc_context);
      break;
    case coin2::exchange::uniswap::api_order::grpc::GRPC_QUERY_NFT_TOKEN_INFO:
      ParseNftTokenInfo(grpc_context);
      break;
    case coin2::exchange::uniswap::api_order::grpc::GRPC_PLACE_ORDER:
      ParseSubmitResponse(grpc_context);
      break;
    case coin2::exchange::uniswap::api_order::grpc::GRPC_PROVIDE_LIQUIDITY:
      ParseProvideLiquidity(grpc_context);
      break;
    case coin2::exchange::uniswap::api_order::grpc::GRPC_WITHDRAW_LIQUIDITY:
      ParseWithdrawLiquidity(grpc_context);
      break;
    default:
      LOG(ERROR) << "[ParseRawMessage] Unknown query type\n" << msg_->DebugString();
      result_.set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
      break;
  }

  return result_;
}

}  // namespace coin2::exchange::uniswap::order_v3
